package itsm.isperp.framework.template;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 模板 {@link Template}分析器，在模板定以后，根据模板的source及context进行解析
 * 
 * @author lizx
 * @date 2013-9-1
 * @since 1.0
 * 
 */
class TemplateParser implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * 类型转换
	 */
	enum TRANSFORMATIONS {
		SIZE, FORMAT
	}

	// 路径分割正则式
	public static final String PATH_SEPARATOR = "\\.(?=([^\"]*\"[^\"]*\")*[^\"]*$)";
	// 变量正则式
	public static final Pattern VARIABLE_PATTERN = Pattern
			.compile("\\$\\{(.*?)}");
	// 列表正则式
	public static final Pattern ITERATE_PATTERN = Pattern.compile(
			"\\[#list (.*?) as (.*?)](.*?)\\[/#list]", Pattern.DOTALL);
	public static final Pattern ARRAY_PATTERN = Pattern
			.compile(".*\\[(\\d*)\\]$");
	// 方法转换正则式,size/format方法
	public static final Pattern TRANSFORM_PATTERN = Pattern
			.compile(".*\\?(.*)$");
	// 变量正则式
	public static final Pattern TRANSFORM_ARG_PATTERN = Pattern
			.compile(".*\\((.*)\\)$");
	public static final Pattern DEFAULT_PATTERN = Pattern.compile(".*!(.*)$");

	private Template template;

	/**
	 * 构造模板分析器{@link TemplateParser}
	 * 
	 * @param template
	 *            模板
	 */
	public TemplateParser(Template template) {
		this.template = template;
	}

	/**
	 * 渲染结果内容
	 * 
	 * @return 结果内容
	 * @throws TemplateException
	 */
	public String render() throws TemplateException {
		// 迭代循环表达式
		Matcher matcher = ITERATE_PATTERN.matcher(template.getSource());
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {
			String listProperty = matcher.group(1).trim();
			String listVariable = matcher.group(2).trim();
			String content = matcher.group(3);

			// 在迭代中提取内容并进行解析
			matcher.appendReplacement(sb,
					expandIterator(listProperty, listVariable, content));
		}
		matcher.appendTail(sb);

		// 再一次解析整个源以便扩大外部的变量
		return renderPart(sb.toString());
	}

	/**
	 * 在每个迭代循环解析内容
	 * 
	 * @param listProperty
	 *            寻找的列表属性
	 * @param listVariable
	 *            列表中每一个输入体的变量名
	 * @param content
	 *            [#list] [/#list] 之间的內容
	 * @return 一个扩展字符串解析的内容
	 * @throws TemplateException
	 */
	private String expandIterator(String listProperty, String listVariable,
			String content) throws TemplateException {
		StringBuilder sb = new StringBuilder();
		Collection<Object> list;

		Object listObject = lookupContext(listProperty);
		if (listObject == null)
			throw new TemplateException(
					"Trying to iterate over unexisting property "
							+ listProperty, template);

		if (listObject instanceof Collection)
			list = (Collection) listObject;
		else if (listObject instanceof Object[]) {
			list = new ArrayList<>();
			list.addAll(Arrays.asList((Object[]) listObject));
		} else
			throw new TemplateException("Trying to iterate over property "
					+ listProperty + " which is not a list or array", template);

		for (Object object : list) {
			Object existingVariable = template.getContext().get(object);
			template.getContext().put(listVariable, object);
			sb.append(renderPart(content).replaceAll("^\n", ""));
			template.getContext().put(listVariable, existingVariable);
		}
		return sb.toString();
	}

	private String renderPart(String partSource) throws TemplateException {
		Matcher matcher = VARIABLE_PATTERN.matcher(partSource);
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {

			matcher.appendReplacement(sb,
					convert(lookupContext(matcher.group(1))));
		}
		matcher.appendTail(sb);
		return sb.toString();
	}

	/**
	 * 在context上下文找寻找制定的表达式
	 * 
	 * @param pathExpression
	 *            表达式
	 * @return 表达式对于的对象
	 * @throws TemplateException
	 *             模板异常
	 */
	private Object lookupContext(String pathExpression)
			throws TemplateException {
		String[] path = pathExpression.split(PATH_SEPARATOR);
		Object resolved = findPropertyInObject(template.getContext(), path[0]);

		StringBuilder resolvePath = new StringBuilder();

		for (int i = 1; i < path.length; i++) {
			String pathEntry = path[i];
			if (resolvePath.length() > 0)
				resolvePath.append(PATH_SEPARATOR);

			resolvePath.append(pathEntry);
			resolved = findPropertyInObject(resolved, pathEntry);
		}

		return resolved;
	}

	private Object findPropertyInObject(Object object, String property)
			throws TemplateException {
		// 提取Array/List的表达式如果找到则重写其属性表达式
		Matcher arrayMatcher = ARRAY_PATTERN.matcher(property);
		Integer arrayIndex = null;
		if (arrayMatcher.matches()) {
			arrayIndex = Integer.valueOf(arrayMatcher.group(1));
			property = property.replaceAll("\\[" + arrayIndex + "\\]$", "");
		}

		// 需找默认表达式如果找到则其重写属性表达式
		String defaultExpression = null;
		Matcher defaultMatcher = DEFAULT_PATTERN.matcher(property);
		if (defaultMatcher.matches()) {
			defaultExpression = defaultMatcher.group(1);
			property = property.replaceAll("!.*$", "");
		}

		// 寻找转换对象
		String transformExpression = null;
		Matcher transformMatcher = TRANSFORM_PATTERN.matcher(property);
		if (transformMatcher.matches()) {
			transformExpression = transformMatcher.group(1);
			property = property.replaceAll("\\?.*$", "");
		}

		Object value;

		// 找到map
		if (object instanceof Map) {
			value = ((Map) object).get(property);
			if (value != null)
				return postProcessValue(property, arrayIndex,
						transformExpression, value);
		}

		// 找到getter
		try {
			Method getter = object.getClass().getDeclaredMethod(
					convertPropertyToGetter(property));
			value = getter.invoke(object);
			if (value != null)
				return postProcessValue(property, arrayIndex,
						transformExpression, value);
		} catch (Exception noGetter) {
		}

		// 找到公开的字段
		try {
			Field field = object.getClass().getDeclaredField(property);
			value = field.get(object);
			if (value != null)
				return postProcessValue(property, arrayIndex,
						transformExpression, value);
		} catch (Exception noProperty) {

		}

		// 支持无参数的方法
		try {
			Method method = object.getClass().getDeclaredMethod(property);
			value = method.invoke(object);
			if (value != null)
				return postProcessValue(property, arrayIndex,
						transformExpression, value);
		} catch (Exception ex) {
		}

		// 使用默认表达式
		if (defaultExpression != null)
			return expandDefaultExpression(defaultExpression);

		return "";
	}

	private Object expandDefaultExpression(String defaultExpression)
			throws TemplateException {
		// 如果是引号或者双引号则输出为字符串文字
		if (defaultExpression.startsWith("\"")
				&& defaultExpression.endsWith("\"")
				|| defaultExpression.startsWith("'")
				&& defaultExpression.endsWith("'"))
			return stripQuotes(defaultExpression);

		return lookupContext(defaultExpression);
	}

	private String stripQuotes(String defaultExpression) {
		return defaultExpression.substring(1, defaultExpression.length() - 1);
	}

	private Object postProcessValue(String property, Integer arrayIndex,
			String transformExpression, Object object) throws TemplateException {
		if (arrayIndex == null)
			return transform(transformExpression, object);

		if (object instanceof List)
			return transform(transformExpression,
					((List) object).get(arrayIndex));

		if (object instanceof Object[])
			return transform(transformExpression,
					((Object[]) object)[arrayIndex]);

		throw new TemplateException("Property " + property
				+ " has array reference but is not List or Array", template);
	}

	private Object transform(String transformExpression, Object object)
			throws TemplateException {
		if (transformExpression == null)
			return object;

		// 寻找转换
		Matcher argMatcher = TRANSFORM_ARG_PATTERN.matcher(transformExpression);
		String args = null;
		if (argMatcher.matches()) {
			args = stripQuotes(argMatcher.group(1));
			transformExpression = transformExpression.replace(
					"(" + argMatcher.group(1) + ")", "");
		}

		switch (TRANSFORMATIONS.valueOf(transformExpression.toUpperCase())) {
		case SIZE:
			if (object instanceof Object[])
				return ((Object[]) object).length;
			if (object instanceof List)
				return ((List) object).size();
			if (object instanceof Map)
				return ((Map) object).size();
			if (object instanceof String)
				return ((String) object).length();

			throw new TemplateException(
					"Don't know how to return size for object of class "
							+ object.getClass(), template);

		case FORMAT:
			if (args == null)
				throw new TemplateException(
						"No arguments given to format transformation:"
								+ transformExpression, template);
			if (object instanceof Date || object instanceof java.sql.Date
					|| object instanceof java.sql.Time)
				return new SimpleDateFormat(args).format(object);

		default:
			throw new TemplateException("Unknown transform encountered: ?"
					+ transformExpression, template);
		}
	}

	private String convertPropertyToGetter(String property) {
		return "get" + property.substring(0, 1).toUpperCase()
				+ property.substring(1);
	}

	private String convert(Object obj) {
		return obj.toString().replaceAll("\\$", "#");
	}
}